goio包提供了ReadFull / ReadAtLeast函数对Reader对象进行读操作,任何实现io.Reader接口的对象都可以使用这两个方法,同时还延伸出io.EOF / io.ErrUnexpectedEOF错误,下面实践一下。

io.Reader Interface & io.Reader.Read

Reader对象必须实现了io.Reader接口,此接口约定了Read方法,实现此方法的对象都可以使用go io提供的其他方法进行读操作,比如 ReadAtLeast/ReadFullio.Reader.Read方法的实现规则如下:

// 尝试读取 len(p) 字节的数据 并返回实际读取到的字节数 n 和 err
// 当 n > 0 时,err = nil,n <= len(p)
// 当 n = 0 时,err = EOF (内容为空 或 内容读取完)
type Reader interface {
    Read(p []byte) (n int, err error)
}

io.Reader 对象

io.Reader对象通过Read方法将尝试读取len(p)字节的数据放入p []byte中,并返实际读取到的字节数 nerrerrnilio.EOF,具体的返回规则如下:

  1. 如果 n == len(p)0 < n < len(p),则 errnil(即至少读到了一些东西)。
  2. 如果 内容为空没有剩余未读的内容了,则应返回io.EOF错误。

封账一个Reader对象

type StringReader struct {
    s      []byte // content
    cursor int    // latest read position
    len    int    // content length
}

func NewStringReader(content string) *StringReader {
    contentByte := []byte(content)
    return &StringReader{s: contentByte, cursor: -1, len: len(contentByte)}
}

// StringReader 实现 io.Reader 接口的 Read 方法
func (s *StringReader) Read(p []byte) (n int, err error) {
    nextIndex := s.cursor + 1
    lr, lp := len(s.s[nextIndex:]), len(p)

    // 游标已到内容尾部
    if s.cursor == (s.len - 1) {
        return 0, io.EOF
    }

    if lr <= lp { // 剩余可读取内容小于暂存区长度 则全量读取
        n = copy(p, s.s[nextIndex:])
        s.cursor = s.len - 1
        return n, nil
    } else { // 剩余可读取内容大于暂存区长度 则部分读取
        n = copy(p, s.s[nextIndex:(nextIndex + lp + 1)])
        s.cursor += lp
        return lp, nil
    }
}

// reset cursor
func (s *StringReader) Reset() {
    s.cursor = -1
}

func main() {

    // 5 bytes 的存储区
    strTmp := make([]byte, 5)
    
    // 遵循 io.Reader 接口
    var myStrReader io.Reader
    myStrReader = NewStringReader("my string reader")
    
    n, err := myStrReader.Read(strTmp)
    fmt.Printf("%s %d %v \n", strTmp[:n], n, err)

    n, err = myStrReader.Read(strTmp)
    fmt.Printf("%s %d %v \n", strTmp[:n], n, err)

    n, err = myStrReader.Read(strTmp)
    fmt.Printf("%s %d %v \n", strTmp[:n], n, err)

    n, err = myStrReader.Read(strTmp)
    fmt.Printf("%s %d %v \n", strTmp[:n], n, err)
    
    // run result
    // my st       5 <nil>
    // ring        5 <nil>
    // reade       5 <nil>
    // r           1 <nil>
    //             0 EOF
    
    myStrReader.Reset()
    n, err = io.ReadFull(myStrReader, strTmp)
    fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
}

io.ReadAtLeast / io.ReadFull

go提供了两个io函数对Reader对象做更强大的读取模式,其实还是围绕io.Reader.Read方法进行的,所以如果想让自己的Reader对象也能正确的被这两个函数使用,一定要按上文所说的准则实现。

// 断言最少读
func io.ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
    ...
    n, err = r.Read(buf[:])
    ...
}
// 断言全量读
func io.ReadFull(r Reader, buf []byte) (n int, err error) {
    return ReadAtLeast(r, buf, len(buf))
}

这两个函数在操作Reader对象的过程中,产生了一个新的错误态:io.ErrUnexpectedEOF

  1. io.ReadAtLeast 贪婪读,至少读 min 个即视为成功,尽可能的读 len(buf)
    当读取的内容字节数 n == 0 时,err = io.EOF
    当 0 < n < min 时,err = io.ErrUnexpectedEOF
    当 n >= min 时,err = nil
  2. io.ReadFull 断言读,必须读 len(buf) 才视为成功
    当读取的内容字节数 n == 0 时,err = io.EOF
    当 0 < n < len(buf) 时,err = io.ErrUnexpectedEOF
    当 n == len(buf) 时,err = nil
func main() {
    // 5 bytes 的存储区
    strTmp := make([]byte, 5)
    
    // 遵循 io.Reader 接口
    var myStrReader io.Reader
    
    // 内容 10 bytes
    // 两次读取尽 第 3 次时会返回 EOF
    myStrReader = NewStringReader("1234567890")
    
    n, err = io.ReadFull(myStrReader, strTmp)
    fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
    n, err = io.ReadFull(myStrReader, strTmp)
    fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
    n, err = io.ReadFull(myStrReader, strTmp)
    fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
    
    // 内容 11 bytes
    // 第3次读取时只能读取到 1 byte
    // 不足 len(strTmp) 所以会返回 ErrUnexpectedEOF 此时内容已读尽
    // 第4次读取会返回 EOF 错误
    myStrReader = NewStringReader("12345678901")

    n, err = io.ReadFull(myStrReader, strTmp)
    fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
    n, err = io.ReadFull(myStrReader, strTmp)
    fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
    n, err = io.ReadFull(myStrReader, strTmp)
    fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
    n, err = io.ReadFull(myStrReader, strTmp)
    fmt.Printf("%s %d %v \n", myStrTmp[:n], n, err)
}

io.EOF / io.ErrUnexpectedEOF

io.EOF

io.EOF 是在没有任何可读取的内容时触发,比如某文件Reader对象,文件本身为空,或者读取若干次后,文件指针指向了末尾,调用Read都会触发EOF

io.ErrUnexpectedEOF

io.ErrUnexpectedEOF是在设定了一次读取操作时应读取到的期望字节数阈值ReadAtLeast min / ReadFull len(buf))时,读取到了n个字节,且 0 < n < 期望字节数阈值 时,则会返回 io.ErrUnexpectedEOF,即有内容,但不足 最小阈值字节数,没能按期望读取足量的内容到就EOF了,所以 ErrUnexpectedEOF。如果没有内容了,即 n == 0,则会返回 io.EOF

参考Reader 对象的 Read方法,没有期望字节数阈值的设定,只有在没有读取到内容0 == n时则视为io.EOF,否则不视为发生错误。

所以如果使用ReadAtLeast/ ReadFull时一定要捕获io.EOF / io.ErrUnexpectedEOF两个错误,这两个错误其实都是读取完毕的状态。

io.ReaderAtLeast 源码解读

ReaderAtLeastReader 对象的内容读取到 buf 中,并设定至少应读取 min 个字节的阈值。

  1. len(buf) < min,则返回io.ErrShortBuffer,因为缓冲区装不下最小读取量啊!
  2. 当读取的内容长度n不足min时,如果n == 0,说明Reader对象内容为空,则返回io.EOF错误;如果 0 < n < min,说明只读取到了部分数据,则返回io.ErrUnExpectedEOF
  3. 当且仅当n >= min时,返回的err应为nil,即便此时Reader对象Read内容时发生了错误,错误也应该被丢弃,为什么呢?贪婪读,当条件满足贪婪的最低条件后,后续即便发生了错误,此次贪婪读也已经被满足,所以无错,返回已经正常读取的n字节的数据即可。
// ReadAtLeast reads from r into buf until it has read at least min bytes.
// It returns the number of bytes copied and an error if fewer bytes were read.
// The error is EOF only if no bytes were read.
// If an EOF happens after reading fewer than min bytes,
// ReadAtLeast returns ErrUnexpectedEOF.
// If min is greater than the length of buf, ReadAtLeast returns ErrShortBuffer.
// On return, n >= min if and only if err == nil.
// If r returns an error having read at least min bytes, the error is dropped.
func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
    if len(buf) < min {
        return 0, ErrShortBuffer
    }
    for n < min && err == nil {
        var nn int
        nn, err = r.Read(buf[n:])
        n += nn
    }
    if n >= min {
        err = nil
    } else if n > 0 && err == EOF {
        err = ErrUnexpectedEOF
    }
    return
}

e.g.直观一些

// 构造一个 Reader 对象
strReader := strings.NewReader("hello sqrtcat!")
buf := make([]byte, 6)

// "hello " n = 6 err nil
n, err := io.ReadAtLeast(strReader, buf, 4)
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)

// "sqrtca" n = 6 err nil
n, err := io.ReadAtLeast(strReader, buf, 4)
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)

// "t!"  n = 2 err ErrUnexpectedEOF
// 这里如果把 min 4 改为 min 2 的话则满足了 n >= min 的条件 则返回的错误为 nil
n, err := io.ReadAtLeast(strReader, buf, 4)
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)

// strReader 已为空 n = 0 err EOF
n, err := io.ReadAtLeast(strReader, buf, 4)
fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)

实际应用

strReader := strings.NewReader("hello sqrtcat!")
buf := make([]byte, 6)
for {
    n, err := io.ReadAtLeast(strReader, buf, 4)
    if nil != err {
        // 为什么会有 ErrUnexpectedEOF 呢?
        // 当数据长度 % min == 0 时不会触发 ErrUnexpectedEOF 而是在成功读取 数据长度 / min 次后下一次读取触发 EOF
        // 当数据长度 % min != 0 时则会先触发 ErrUnexpectedEOF 如果继续读的话则会触发 EOF
        if io.EOF == err || io.ErrUnexpectedEOF == err {
            fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)
            break
        }

        log.Panicf("read error: %s \n", err.Error())
    }
    fmt.Printf("%-10s %2d %v \n", buf[:n], n, err)
}

big_cat
1.7k 声望130 粉丝

规范至上